5-5 自定义路由导航守卫
什么是路由导航守卫
路由导航守卫(Route Guard)是指在页面跳转时进行拦截,根据条件判断是否允许用户访问目标页面。最常见的应用场景是登录鉴权——未登录的用户不能访问需要权限的页面。
在 Vue 中有内置的 beforeEach 导航守卫,React Router v6 中没有内置守卫,但可以通过自定义组件轻松实现。
实现思路
核心思路是创建一个 ProtectedRoute 组件,包裹需要权限的页面:
用户访问受保护页面
↓
ProtectedRoute 检查登录状态
↓
已登录 → 渲染目标页面(Outlet)
未登录 → 重定向到登录页面
text
完整实现
1. 登录状态管理
// src/context/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface AuthContextType {
isLoggedIn: boolean;
login: () => void;
logout: () => void;
}
const AuthContext = createContext<AuthContextType>({
isLoggedIn: false,
login: () => {},
logout: () => {},
});
export function AuthProvider({ children }: { children: ReactNode }) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<AuthContext.Provider
value={{
isLoggedIn,
login: () => setIsLoggedIn(true),
logout: () => setIsLoggedIn(false),
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
tsx
2. ProtectedRoute 组件
// src/components/ProtectedRoute.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
export default function ProtectedRoute() {
const { isLoggedIn } = useAuth();
if (!isLoggedIn) {
// 未登录,重定向到登录页面
// state 保存原始目标路径,登录后可跳回
return <Navigate to="/login" replace />;
}
// 已登录,渲染子路由
return <Outlet />;
}
tsx
3. 登录页面
// src/pages/Login.tsx
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
export default function Login() {
const { login } = useAuth();
const navigate = useNavigate();
const location = useLocation();
// 获取登录前的目标路径
const from = (location.state as { from?: string })?.from || '/';
const handleLogin = () => {
login(); // 更新登录状态
navigate(from, { replace: true }); // 跳转到原始目标页面
};
return (
<div>
<h1>登录页面</h1>
<button onClick={handleLogin}>模拟登录</button>
</div>
);
}
tsx
4. 路由配置
// src/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
import Layout from './components/Layout';
import Home from './pages/Home';
import About from './pages/About';
import Dashboard from './pages/Dashboard';
import Login from './pages/Login';
import NotFound from './pages/NotFound';
import { useAuth } from './context/AuthContext';
// 导航栏:根据登录状态显示不同内容
function Nav() {
const { isLoggedIn, logout } = useAuth();
return (
<nav>
<Link to="/">首页</Link> | {' '}
<Link to="/about">关于</Link>
{isLoggedIn && <Link to="/dashboard">控制台</Link>}
{isLoggedIn
? <button onClick={logout}>退出登录</button>
: <Link to="/login">登录</Link>
}
</nav>
);
}
export default function App() {
return (
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="login" element={<Login />} />
{/* 受保护的路由 */}
<Route element={<ProtectedRoute />}>
<Route path="dashboard" element={<Dashboard />} />
{/* 可以在这里添加更多受保护的路由 */}
</Route>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
);
}
tsx
5. 保存来源路径的优化版
// src/components/ProtectedRoute.tsx(优化版)
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
export default function ProtectedRoute() {
const { isLoggedIn } = useAuth();
const location = useLocation();
if (!isLoggedIn) {
// 将当前路径通过 state 传递给登录页,登录后跳回
return <Navigate to="/login" state={{ from: location.pathname }} replace />;
}
return <Outlet />;
}
tsx
工作流程示意
未登录用户访问 /dashboard
↓
ProtectedRoute 检测 isLoggedIn = false
↓
Navigate to /login (state: { from: '/dashboard' })
↓
用户在登录页面完成登录
↓
login() 更新 isLoggedIn = true
↓
navigate(state.from) → 跳转到 /dashboard
↓
ProtectedRoute 检测 isLoggedIn = true
↓
渲染 <Outlet /> → Dashboard 组件
text
更复杂的权限控制
如果需要角色级别的权限控制(如管理员/普通用户),可以扩展 ProtectedRoute:
interface ProtectedRouteProps {
allowedRoles?: string[];
}
export default function ProtectedRoute({ allowedRoles }: ProtectedRouteProps) {
const { isLoggedIn, user } = useAuth();
const location = useLocation();
if (!isLoggedIn) {
return <Navigate to="/login" state={{ from: location.pathname }} replace />;
}
if (allowedRoles && !allowedRoles.includes(user.role)) {
return <Navigate to="/403" replace />; // 无权限页面
}
return <Outlet />;
}
// 路由配置
<Route element={<ProtectedRoute allowedRoles={['admin']} />}>
<Route path="admin" element={<AdminPanel />} />
</Route>
tsx
这种自定义路由守卫的模式灵活且可扩展,适用于各种权限控制场景。
↑